package com.jd.opendj.api.client.feature;

import com.google.gson.JsonSyntaxException;
import com.jd.opendj.api.OpenDjRequest;
import com.jd.opendj.api.OpenDjResponse;
import com.jd.opendj.api.exception.FailedRequestException;
import com.jd.opendj.api.exception.LimitedException;
import com.jd.opendj.api.exception.RetryException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * <p>AutoRetryFeature</p>
 * 自动重试
 * @apiNote 参考 taobaosdk
 *
 *
 * @author 邓峰峰
 * @date 9/8/2020 5:50 PM
 */
public abstract class AbstractAutoRetryFeature {

    private static final Log log = LogFactory.getLog(AbstractAutoRetryFeature.class);
    private static final RetryException RETRY_FAIL = new RetryException("重试次数超过最大重试次数限制");
    private static final LimitedException LIMIT_FAIL = new LimitedException("超频异常");


    /**
     * 最大重试次数
     */
    private int maxRetryCount = 3;
    /**
     * 重试等待时间(毫秒)
     */
    private long retryWaitTime = 500L;
    /**
     * 重试次数超多最大重试次数,是否抛出异常
     */
    private boolean throwIfOverMaxRetry = false;

    /**
     * 超频异常
     */
    private String limitCode = "10032";

    /**
     * 超频重试等待时间
     */
    private long limitRetryWaitTime = 60_000L;
    /**
     * 需要重试的异常码
     */
    private Set<String> retryErrorCodes = new HashSet<>(Arrays.asList("-1","-10000","10000","100022","100023","100024","10025","10028"));

    private void sleepWithoutInterrupt(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private String buildRetryLog(String apiName, String params, int retryCount) {
        StringBuilder sb = new StringBuilder();
        sb.append(apiName).append(" retry call ").append(retryCount);
        sb.append(" times, params=").append(params);
        return sb.toString();
    }


    public <T extends OpenDjResponse> T execute(OpenDjRequest<T> request) throws FailedRequestException {
        T rsp = null;
        FailedRequestException exp = null;

        for (int i = 0; i <= maxRetryCount; i++) {
            if (i > 0) {
                if ((rsp != null && ((retryErrorCodes != null && retryErrorCodes.contains(rsp.getCode())))) || exp != null) {

                    sleepWithoutInterrupt(retryWaitTime);
                    log.warn(buildRetryLog(request.api().name(), request.toJsonString(), i));
                } else if(limitCode.equals(rsp.getCode())){
                    sleepWithoutInterrupt(limitRetryWaitTime);
                    log.warn(buildRetryLog(request.api().name(), request.toJsonString(), i));
                } else {
                    break;
                }
            }

            try {
                rsp = clientExecute(request);
                if (rsp.isSuccess()) {
                    return rsp;
                } else {
                    if (i == maxRetryCount && throwIfOverMaxRetry) {
                        if(limitCode.equals(rsp.getCode())){
                            throw LIMIT_FAIL;
                        }
                        if(retryErrorCodes.contains(rsp.getCode())){
                            throw RETRY_FAIL;
                        }
                        throw RETRY_FAIL;
                    }
                }
            } catch (FailedRequestException e) {
                if (exp == null) {
                    exp = e;
                }
                //如果是京东到家给的字符串无法转换成Java对象,则重试1次,经常出现
            } catch (JsonSyntaxException e) {
                if (exp == null) {
                    exp = new FailedRequestException(e.getLocalizedMessage());
                }
            }
        }

        if (exp != null) {
            throw exp;
        } else {
            return rsp;
        }
    }

    /**
     * 执行公开API请求。
     *
     * @param <T> 具体的API响应类
     * @param request 具体的API请求类
     * @return 具体的API响应
     * @throws FailedRequestException 请求失败的异常
     */
    public abstract <T extends OpenDjResponse> T clientExecute(OpenDjRequest<T> request) throws FailedRequestException;

    public int getMaxRetryCount() {
        return maxRetryCount;
    }

    public void setMaxRetryCount(int maxRetryCount) {
        this.maxRetryCount = maxRetryCount;
    }

    public long getRetryWaitTime() {
        return retryWaitTime;
    }

    public void setRetryWaitTime(long retryWaitTime) {
        this.retryWaitTime = retryWaitTime;
    }

    public boolean isThrowIfOverMaxRetry() {
        return throwIfOverMaxRetry;
    }

    public void setThrowIfOverMaxRetry(boolean throwIfOverMaxRetry) {
        this.throwIfOverMaxRetry = throwIfOverMaxRetry;
    }

    public Set<String> getRetryErrorCodes() {
        return retryErrorCodes;
    }

    public void setRetryErrorCodes(Set<String> retryErrorCodes) {
        this.retryErrorCodes = retryErrorCodes;
    }

    public String getLimitCode() {
        return limitCode;
    }

    public void setLimitCode(String limitCode) {
        this.limitCode = limitCode;
    }

    public long getLimitRetryWaitTime() {
        return limitRetryWaitTime;
    }

    public void setLimitRetryWaitTime(long limitRetryWaitTime) {
        this.limitRetryWaitTime = limitRetryWaitTime;
    }
}

